package com.gmcloud.common.mybatis.plus;


import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.gmcloud.common.mybatis.annotation.QueryAnnotation;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Supplier;

import static java.lang.invoke.LambdaMetafactory.FLAG_SERIALIZABLE;

/**
 * @author zl.sir
 * @version 1.0
 * @since 2023-03-16 13:55
 * @description 查询条件生成
 */
public class QueryWrapperPlus {

    private QueryWrapperPlus() {
    }

    private static final Logger log = LoggerFactory.getLogger(QueryWrapperPlus.class);

    public static <T, Q> Wrapper<T> getPredicate(@NotNull Q query, Supplier<T> supplier) {
        LambdaQueryWrapper<T> queryWrapper = new LambdaQueryWrapper<>(supplier.get());
        List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());

        for (Field field : fields) {
            boolean accessible = field.canAccess(query);
            ReflectionUtils.makeAccessible(field);

            QueryAnnotation queryAnnotation = field.getAnnotation(QueryAnnotation.class);
            SFunction<T, ?> functionField;

            try {
                Object val = field.get(query);

                // 默认不查询
                if (Objects.isNull(queryAnnotation)) {
                    continue;
                }

                String fieldName = queryAnnotation.fieldName();
                functionField = getFunctionField(query.getClass(), field, fieldName);

                boolean condition = !queryAnnotation.ignore();
                boolean isAsc = queryAnnotation.isAsc();

                switch (queryAnnotation.type()) {
                    case EQUAL -> {
                        if (Objects.isNull(val)) {
                            queryWrapper.isNull(condition, functionField);
                        } else {
                            queryWrapper.eq(condition, functionField, val);
                        }
                    }
                    case GREATER_THAN -> queryWrapper.ge(condition, functionField, val);
                    case LESS_THAN -> queryWrapper.le(condition, functionField, val);
                    case LESS_THAN_NQ -> queryWrapper.lt(condition, functionField, val);
                    case GREATER_THAN_NQ -> queryWrapper.gt(condition, functionField, val);
                    case LIKE -> queryWrapper.like(condition, functionField, val);
                    case LEFT_LIKE -> queryWrapper.likeLeft(condition, functionField, val);
                    case RIGHT_LIKE -> queryWrapper.likeRight(condition, functionField, val);
                    case IN -> {
                        List<?> in = castList(val, field.getClass());
                        if (!CollectionUtils.isEmpty(in)) {
                            queryWrapper.in(condition, functionField, in);
                        }
                    }
                    case NOT_EQUAL -> queryWrapper.ne(condition, functionField, val);
                    case NOT_NULL -> queryWrapper.isNotNull(condition, functionField);
                    case BETWEEN -> {
                        List<?> between = castList(val, field.getClass());
                        queryWrapper.between(condition, functionField, between.get(0), between.get(1));
                    }
                    case GROUP_BY -> queryWrapper.groupBy(condition, functionField);
                    case ORDER_BY -> queryWrapper.orderBy(condition, isAsc, functionField);
                    case IS_NULL -> queryWrapper.isNull(condition, functionField);
                    default -> log.info("sql不支持此查询方式");
                }

                field.setAccessible(accessible);
            } catch (Exception e) {
                log.error("映射查询条件异常:{},{}", e.getMessage(), e);
            }

        }
        return queryWrapper;
    }

    private static <T> List<Field> getAllFields(Class<? super T> clazz, List<Field> fields) {
        while (clazz != null) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    public static <T, R> SFunction<T, R> getFunctionField(Class<?> entityClass, Field field, String filedName) {
        final MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType methodType = MethodType.methodType(field.getType(), entityClass);
        final CallSite site;
        if (StringUtils.isEmpty(filedName)) {
            filedName = field.getName();
        }
        String getFunName = "get" + filedName.substring(0, 1).toUpperCase() + filedName.substring(1);
        try {
            site = LambdaMetafactory.altMetafactory(lookup,
                    "invoke",
                    MethodType.methodType(SFunction.class),
                    methodType,
                    lookup.findVirtual(entityClass, getFunName, MethodType.methodType(field.getType())),
                    methodType, FLAG_SERIALIZABLE);
            return (SFunction<T, R>) site.getTarget().invokeExact();
        } catch (Throwable e) {
            throw ExceptionUtils.mpe("This class %s is not have method %s ", entityClass.getName(), getFunName);
        }
    }

    /**
     * object转集合
     *
     * @param obj   对象
     * @param clazz 类型
     * @param <T>   类型
     * @return list
     */
    public static <T> List<T> castList(Object obj, Class<T> clazz) {
        List<T> result = new ArrayList<>();
        if (obj instanceof List<?>) {
            for (Object o : (List<?>) obj) {
                result.add(clazz.cast(o));
            }
            return result;
        }
        return Collections.emptyList();
    }

}
